#include "pgloader.h"
#include "filemanager.h"

#include <QVector>
#include <QDebug>

#include <QRegularExpression>
#include <QtConcurrent>
#include <QGuiApplication>
#include <QFile>
#include <QDir>

#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QException>

#include <QReadLocker>
#include <QWriteLocker>

struct PGLoaderConfig
{
    PGLoaderConfig() : m_modify(false) {}

    bool m_modify;
    QReadWriteLock m_lock;
    QString m_cachePath = "config/pgloader_config.json";
    QString m_extensionPath = "extension/";
    QMap<QString, DllItem> m_dlls;
};

/* @Brief   : 用于初始化PGLoaderConfig
 * @Param   : config 需要初始化的PGLoaderConfig
 * @Return  : true  初始化成功
 *            false 初始失败
 * @Author  : BinWatson
 * @Version : v1.2
 * @Date    : 2022-06-04 21:42:24
 * @Details : 从 config->m_cachePath 读取配置文件，然后根据已经定义的格式
 *            读取 m_cachePath 所指定的文件，进行解析，将解析出来的每一个dll
 *            封装成DllItem存放在 config->m_dlls
 */
bool init(PGLoaderConfig *config)
{
    QFile file(config->m_cachePath);
    // 1. 打印信息，打开m_cachePath文件
    qInfo() << "Reading" << file.fileName();
    qInfo() << "Loading PGLoader's config.";
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qCritical("Can't open file %s",
                  file.fileName().toStdString().data());
        return false;
    }
    qDebug() << "Open file" << file.fileName() << "success!";

    // 2. 读取文件，解析json，并将加载的dll放入tmp中
    QMap<QString, DllItem> tmp;
    QString jsonStr = file.readAll();
    QJsonParseError err_rpt;
    QJsonDocument root_document = QJsonDocument::fromJson(jsonStr.toUtf8(),
                                                          &err_rpt);
    QJsonObject root_object = root_document.object();
    QJsonArray dll_array = root_object.find("dll").value().toArray();
    qDebug() << "Dll count" << dll_array.size();
    for (int i = 0; i < dll_array.size(); ++i) {
        QJsonObject dll_item = dll_array[i].toObject();
        DllItem item = {
            .m_idx      = dll_item.find("idx")->toInt(),
            .m_fileName = dll_item.find("name")->toString(),
            .m_path     = dll_item.find("path")->toString(),
            .m_info     = nullptr // 该域在m_config中始终为空指针
        };
        tmp[item.m_fileName] = item;
    }
    if (err_rpt.error != QJsonParseError::NoError) {
        qCritical() << "Parse error" << err_rpt.errorString();
        file.close();
        return false;
    }

    // 3. 将加载的dll拷贝到config->m_dlls
    {
        QWriteLocker lock(&config->m_lock);
        config->m_dlls = std::move(tmp);
    }
    qInfo() << "Load PGLoader's config success!";
    file.close();
    return true;
}

/* @Brief   : 将PGLoaderConfig写入配置文件中
 * @Param   : config 需要写入的PGLoaderConfig
 * @Return  : void
 * @Author  : BinWatson
 * @Version : v1.0
 * @Date    : 2022-06-03 09:08:15
 * @Details : 从 config 读取配置信息，根据已经定义的格式封装成json然后
 *            读取 m_cachePath 所指定的文件，将json重新写入配置文件中
 */
void store(PGLoaderConfig *config)
{
    // 1. 将dll拷贝到tmp中
    QMap<QString, DllItem> tmp;
    {
        QReadLocker lock(&config->m_lock);
        tmp = config->m_dlls;
    }

    // 2. 封装成json
    QJsonObject root_object;
    QJsonArray dll_array;
    for (const DllItem &dll : tmp) {
        QJsonObject obj;
        obj["idx"]  = dll.m_idx;
        obj["name"] = dll.m_fileName;
        obj["path"] = dll.m_path;
        dll_array.push_back(obj);
    }
    root_object["dll"] = dll_array;

    // 3. 写入文件
    QFile file(config->m_cachePath);
    qInfo() << "Writing to" << file.fileName();
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qCritical("Can't open file %s",
                  file.fileName().toStdString().data());
        return;
    }
    QJsonDocument jsonDoc(root_object);
    file.write(jsonDoc.toJson());
    file.waitForBytesWritten(-1);   // 阻塞写入
    qDebug() << "Write" << config->m_cachePath << "complete!";
    config->m_modify = false;
}


bool handleError(const QUrl url)
{
    QString filePath = url.toLocalFile();
    static QRegularExpression re("dll$");

    if (!url.isLocalFile())
    {
        qCritical() << "Only useful for local file.";
        return false;
    }
    else if (!re.match(filePath).hasMatch())
    {
        qCritical() << "File type is not dll for" << filePath;
        return false;
    }


    QLibrary load;
    load.setFileName(filePath);
    // 加载测试
    if (!load.load())
    {
        qCritical() << load.errorString().toStdString().data();
        return false;
    }
    // 接口测试
    ReleasePluginInfo release = (ReleasePluginInfo)load.resolve("releasePluginInfo");
    GetPluginInfo getPluginInfo = (GetPluginInfo)load.resolve("getPluginInfo");
    if (!release || !getPluginInfo)
    {
        qCritical() << load.errorString().toStdString().data();
        return false;
    }
    // 卸载，只是进行测试
    load.unload();

    return true;
}


PGLoader *PGLoader::instance()
{
    return Sington<PGLoader>::getInstance();
}

PGLoader::~PGLoader()
{
    for (int i = 0; i < m_libs.size(); ++i) {
        ReleasePluginInfo release = (ReleasePluginInfo)m_libs[i]->resolve("releasePluginInfo");
        release();
        m_libs[i]->unload();    // 卸载动态库
    }
    m_thread->exit();
    delete m_thread;
}

void PGLoader::load(PGEngine &engine)
{
    if (init(m_config))
    {
        QWriteLocker lock1(&m_lock);     // 操作m_dllItems
        QWriteLocker lock2(&m_config->m_lock);

        m_items.resize(m_config->m_dlls.size());
        for (auto& dll : m_config->m_dlls)
        {
            qInfo() << "Loading" << dll.m_fileName;

            std::shared_ptr<QLibrary> load(new QLibrary(dll.m_path));
            // 加载动态库xxxx.dll
            if (load->load())
            {
                GetPluginInfo getPluginInfo = (GetPluginInfo)load->resolve("getPluginInfo");
                // 获取接口函数
                if (getPluginInfo)
                {
                    qDebug() << "Cast GetPGExtensionInfo success!";
                    PluginInfo* info = getPluginInfo(&engine);
                    // 防止溢出
                    if (dll.m_idx >= m_items.size())
                        m_items.resize(dll.m_idx + 1);
                    // 添加DllItem到m_dllItems，按照m_idx顺序存放
                    m_items[dll.m_idx] = dll;
                    m_items[dll.m_idx].m_info = info;
                    qInfo() << "Load" << dll.m_fileName << "success!";

                    // 存放加载的库
                    m_libs.push_back(std::move(load));
                }
                else
                {
                    qCritical() << "Load" << dll.m_fileName
                                << "failure, can't resolve GetPGExtensionInfo.";
                    load->unload();
                }
            }
            else
            {
                qCritical() << load->errorString().toStdString().data();
            }
        }
        qInfo() << "All extension components have been loaded!";
    }
    emit loadComplete();
}

void PGLoader::addExtensionDll(const QUrl& url)
{
    if (!runInLoop())
    {
        QMetaObject::invokeMethod(this, "addExtensionDll",
                                  Q_ARG(const QUrl&, url));
        return;
    }
    QtConcurrent::run([=](){
        // 1. 错误处理
        if (!handleError(url))
            return;

        // 2. 提取文件名
        QString oldFilePath = url.toLocalFile();
        QString newFilePath = m_config->m_extensionPath + url.fileName();   // 相对路径
        qDebug() << newFilePath;

        // 3. 将dll拷贝到extension目录下面
        QDir dir;
        if (!dir.exists(m_config->m_extensionPath))
        {
            dir.mkpath(m_config->m_extensionPath);
        }

        QFile oldFile(oldFilePath);
        if (oldFile.open(QIODevice::ReadOnly))
        {
            // 原文件已经存在
            // 那么该dll已经被加载进程序了
            // 3.1 需要卸载dll解除占用
            // 3.2 删除原文件
            // 3.3 强制重启，不然原dll可能已经启动某些服务
            if (QFile::exists(newFilePath))
            {
                oldFile.close();
                // 此处没有加锁访问
                // 考虑到程序中，只有此处和load()处有对m_libs的操作
                // 而load()发送在程序一开始，所以暂时不考虑冲突的可能性
                for (auto lib : m_libs)
                {
                    // lib中的fileName是相对路径
                    if (lib->fileName() == newFilePath && !lib->unload())
                        qInfo() << lib->errorString();
                }
                QFile::remove(newFilePath);
            }

            // 拷贝文件
            if (oldFile.copy(newFilePath))
            {
                qDebug() << "Copy dll" << oldFilePath.toStdString().data()
                         << "to" << (qApp->applicationDirPath() + "/" + newFilePath).toStdString().data()
                         << "success!";
                emit copyComplete();
            }
            else
            {
                qDebug() << oldFile.errorString();
                return;
            }
            oldFile.close();
            // TODO强制重启
        }

        // 4. 将dll信息写入配置文件
        DllItem nItem = {
            .m_idx      = m_config->m_dlls.size(),   // 插件在配置文件中的下标
            .m_fileName = url.fileName(),                     // 插件的名字
            .m_path     = newFilePath,                // 插件在文件系统中的路径
            .m_info     = nullptr
        };
        {
            QWriteLocker lock(&m_config->m_lock);
            // 出现重名的直接覆盖掉
            if (m_config->m_dlls.contains(nItem.m_fileName))
            {
                nItem.m_idx = m_config->m_dlls[nItem.m_fileName].m_idx;
            }
            m_config->m_dlls[nItem.m_fileName] = std::move(nItem);
            m_config->m_modify = true;  // 修改标志位-->需要更新配置文件
        }
        emit addDllComplete(url);
        // 更新配置信息
        store(m_config);
        // 重启生效
        // 只对m_config中的dll进行了修改，而没有动m_items中的信息
        });
}

DllItem* PGLoader::getItem(int index)
{
    if (index < 0 || index >= m_items.size())
        return nullptr;

    QReadLocker lock(&m_lock);
    return &m_items[index];
}

QList<DllItem> PGLoader::getItems()
{
    QReadLocker lock(&m_lock);
    return QList<DllItem>(m_items.begin(), m_items.end());
}

int PGLoader::itemSize() const
{
    return m_items.size();
}

PGLoader::PGLoader(QObject *parent) :
    QObject(parent),
    m_thread(nullptr),
    m_config(new PGLoaderConfig)
{

}

bool PGLoader::runInLoop() const
{
   return QThread::currentThread() == thread();
}

